package com.itzixi.tools;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import com.itzixi.tools.Entity.AliasProperty;
import com.itzixi.tools.Entity.Property;
import com.itzixi.tools.Entity.SetProperty;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class CodeGenerator {

	public final static String MapperXmlPath = "com.itzixi.mapper";
	public final static String entityPackage = "com.itzixi.pojo";
	public final static String daoPackage = "com.itzixi.mapper";
	public final static String serviceImplPackage = "com.itzixi.service.impl";
	public final static String servicePackage = "com.itzixi.service";
	public final static String actionPackage = "com.itzixi.web.controller";

	public final static String dbUrl = "jdbc:mysql://127.0.0.1:3306/leecx";

	public final static String schemaName = "leecx";

	public final static String dbUsername = "root";

	public final static String dbPassword = "123456";

	public final static String tablePrefix = "";

	public final static String generateTable = "test"; // 为""会则更新整个数据库

	public static void main(String[] args) {
		try {
			Properties prop = new Properties();
			prop.setProperty("user", dbUsername);
			prop.setProperty("password", dbPassword);
			prop.setProperty("remarks", "true");
			Class.forName("com.mysql.jdbc.Driver");
			Connection conn = DriverManager.getConnection(dbUrl, prop);

			DatabaseMetaData dbmd = conn.getMetaData();

			List<Entity> entityList = new ArrayList<Entity>();
			if ("".equals(generateTable)) {
				entityList = getTableEntitys(dbmd);
			} else if (generateTable.contains(",")) {
				String[] tableNames = generateTable.split(",");
				for (String tableName : tableNames) {
					entityList.add(buildTableEntity(tableName, dbmd));
				}
			} else {
				entityList.add(buildTableEntity(generateTable, dbmd));
			}
			conn.close();

			for (Entity entity : entityList) {
				/*
				 * generate(MapperXmlPath, "/src/main/java/", entity.className +
				 * "Mapper.xml", "mapper.xml.ftl", entity, true);
				 * generate(daoPackage, "/src/main/java/", entity.className +
				 * "Mapper.java", "mapper.ftl", entity, false);
				 * generate(entityPackage, "/src/main/java/", entity.className +
				 * ".java", "entity.ftl", entity, false);
				 * generate(servicePackage, "/src/main/java/", entity.className
				 * + "Service.java", "service.ftl", entity, false);
				 * generate(serviceImplPackage, "/src/main/java/",
				 * entity.className + "ServiceImpl.java", "serviceImpl.ftl",
				 * entity, false); generate(actionPackage, "/src/main/java/",
				 * entity.className + "Controller.java", "controller.ftl",
				 * entity, false);
				 */

				generate("", "/src/main/webapp/WEB-INF/center/",
						entity.classInstanceName + "/create" + entity.className + "Page.jsp", "page-create.ftl", entity,
						false);
				generate("", "/src/main/webapp/WEB-INF/center/",
						entity.classInstanceName + "/" + entity.classInstanceName + "Detail.jsp", "page-detail.ftl",
						entity, false);
				generate("", "/src/main/webapp/WEB-INF/center/",
						entity.classInstanceName + "/" + entity.classInstanceName + "List.jsp", "page-list.ftl", entity,
						false);
				generate("", "/src/main/webapp/WEB-INF/center/",
						entity.classInstanceName + "/modify" + entity.className + ".jsp", "page-modify.ftl", entity,
						false);

				generate("", "/src/main/webapp/static/pages/js", entity.classInstanceName + ".js", "page-js.ftl",
						entity, false);
				generate("", "/src/main/webapp/static/pages/js", entity.classInstanceName + "List.js",
						"page-js-list.ftl", entity, false);

			}
		} catch (Throwable t) {
			t.printStackTrace();
		}
		System.out.println("finish");
	}

	public static void generate(String packageName, String prefix,
			String suffix, String templateName, Entity entity, boolean cover)
			throws IOException, TemplateException {
		String packagePath = packageName.replace('.', '/');
		String projectDir = System.getProperty("user.dir");
		String mapperDir = new File(projectDir).getParent()+"/leecx-mng-mapper";
		String serviceDir = new File(projectDir).getParent()+"/leecx-mng-service";
		
		if(MapperXmlPath.equals(packageName) || entityPackage.equals(packageName) || daoPackage.equals(packageName)){
			projectDir = mapperDir;
		}else if (serviceImplPackage.equals(packageName) || servicePackage.equals(packageName)){
			projectDir = serviceDir;
		}
		String path = projectDir + prefix + packagePath + "/" + suffix;
		File file = new File(path);
		if (file.exists()) {
			if (cover == false) {
				return;
			}
		}
		if (!file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}

		Configuration cfg = new Configuration();
		cfg.setDirectoryForTemplateLoading(new File(projectDir
				+ "/src/main/resources/template/code/"));
		Template hbmTemplate = cfg.getTemplate(templateName);
		Writer out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(file), "UTF-8"));
		HashMap<String, Object> dataMap = new HashMap<String, Object>();
		dataMap.put("entity", entity);
		hbmTemplate.process(dataMap, out);
		out.close();
	}

	public static List<Entity> getTableEntitys(DatabaseMetaData dbmd) throws Exception {
		ResultSet rs = dbmd.getTables(null, null, null, new String[] { "table" });
		List<String> tables = new ArrayList<String>();
		while (rs.next()) {
			System.out.println(rs.getString("TABLE_NAME"));
			tables.add(rs.getString("TABLE_NAME"));
		}
		rs.close();

		List<Entity> entityList = new ArrayList<Entity>();
		for (String table : tables) {
			Entity entity = buildTableEntity(table, dbmd);
			if (entity.manyToMany == false) {
				entityList.add(entity);
			}
		}

		return entityList;
	}

	public static Entity buildTableEntity(String table, DatabaseMetaData dbmd) throws Exception {
		Entity entity = new Entity();
		entity.entityPackageName = entityPackage;
		entity.daoPackageName = daoPackage;
		entity.servicePackageImplName = serviceImplPackage;
		entity.servicePackageName = servicePackage;
		entity.actionPackageName = actionPackage;
		entity.tableName = table;
		entity.className = fillUpperName(removePrefix(table, tablePrefix));
		entity.classInstanceName = fillLowerName(removePrefix(table, tablePrefix));
		ResultSet rs = dbmd.getPrimaryKeys(null, null, table);
		while (rs.next()) {
			if (entity.idColumn != null) {
				entity.manyToMany = true;
				return entity;
			}
			entity.idColumn = rs.getString("COLUMN_NAME");
		}
		rs.close();

		rs = dbmd.getImportedKeys(null, null, table);
		while (rs.next()) {
			AliasProperty aprop = new AliasProperty();
			aprop.primaryTableName = rs.getString("PKTABLE_NAME");
			aprop.primaryClassName = fillUpperName(removePrefix(aprop.primaryTableName, tablePrefix));
			aprop.columnName = rs.getString("FKCOLUMN_NAME");
			String str = fillLowerName(aprop.columnName);
			aprop.propName = str.endsWith("Id") ? str.substring(0, str.length() - 2) : str;
			aprop.getMethod = "get" + fillFirstUpper(aprop.propName);
			aprop.setMethod = "set" + fillFirstUpper(aprop.propName);
			entity.aPropList.add(aprop);
		}
		rs.close();

		rs = dbmd.getExportedKeys(null, null, table);
		// List<String> columnNames = new ArrayList<String>(24);
		// ResultSetMetaData rsmd = rs.getMetaData();
		// int columnCount = rsmd.getColumnCount();
		// System.out.println("column count:" + columnCount);
		// for (int i = 1; i <= columnCount; i++) {
		// columnNames.add(rsmd.getColumnName(i));
		// }
		while (rs.next()) {
			// for (String columnName : columnNames) {
			// System.out.println(columnName + ": " + rs.getObject(columnName));
			// }

			if (table.equals(rs.getString("PKTABLE_NAME"))) {
				SetProperty sprop = new SetProperty();

				sprop.foreignTableName = rs.getString("FKTABLE_NAME");
				sprop.foreignClassName = fillUpperName(removePrefix(sprop.foreignTableName, tablePrefix));
				sprop.foreignColumnName = rs.getString("FKCOLUMN_NAME");
				sprop.propName = fillFirstLower(sprop.foreignClassName) + "Set";
				sprop.getMethod = buildGetMethod(sprop.propName);
				sprop.setMethod = buildSetMethod(sprop.propName);

				buildManyToMany(sprop, dbmd);

				entity.sPropList.add(sprop);
				entity.hasSetType = true;
			}
		}
		rs.close();

		/*
		 * String[] types = {"TABLE"}; rs = dbmd.getTables(null, null, table,
		 * types); while (rs.next()) { String remark =
		 * rs.getString("TABLE_NAME"); System.out.println(remark); } rs.close();
		 */

		rs = dbmd.getColumns(null, null, table, "%");
		while (rs.next()) {
			String columnName = rs.getString("COLUMN_NAME");
			if ("hibernate_version".equals(columnName)) {
				entity.hasHibernateVersion = true;
				continue;
			}
			// 对于视图没有主键的问题，默认以第一列为主键 Modified by Squid Hex
			// Now you can generate code by view
			if (entity.idColumn == null) {
				entity.idColumn = columnName;
				entity.idName = fillLowerName(columnName);
				entity.idType = typeMap.get(rs.getString("TYPE_NAME"));
				entity.idSimpleType = simpleTypeMap.get(rs.getString("TYPE_NAME"));
				entity.idGetMethod = buildGetMethod(entity.idName);
				entity.idSetMethod = buildSetMethod(entity.idName);
				entity.idAutoIncrement = "NO".equals(rs.getString("IS_AUTOINCREMENT")) ? false : true;
			} else {
				if (columnName.equals(entity.idColumn)) {
					entity.idName = fillLowerName(entity.idColumn);
					entity.idType = typeMap.get(rs.getString("TYPE_NAME"));
					entity.idSimpleType = simpleTypeMap.get(rs.getString("TYPE_NAME"));
					entity.idGetMethod = buildGetMethod(entity.idName);
					entity.idSetMethod = buildSetMethod(entity.idName);
					entity.idAutoIncrement = "NO".equals(rs.getString("IS_AUTOINCREMENT")) ? false : true;
				} else {
					Property prop = new Property();
					prop.column = columnName;
					prop.propName = fillLowerName(prop.column);
					prop.propType = typeMap.get(rs.getString("TYPE_NAME"));
					if (prop.propType == null) {
						throw new Exception("Unknow Database DataType: " + rs.getString("TYPE_NAME"));
					}
					prop.simpleType = simpleTypeMap.get(rs.getString("TYPE_NAME"));
					if ("BigDecimal".equals(prop.simpleType)) {
						entity.hasDecimalType = true;
					} else if ("Date".equals(prop.simpleType)) {
						entity.hasDateType = true;
					}
					prop.notAllowNull = "NO".equals(rs.getString("IS_NULLABLE"))
							? (rs.getString("COLUMN_DEF") == null ? "true" : "false") : "false";
					prop.note = rs.getString("REMARKS");
					prop.getMethod = buildGetMethod(prop.propName, "Boolean".equals(prop.simpleType));
					prop.setMethod = buildSetMethod(prop.propName);
					for (AliasProperty aprop : entity.aPropList) {
						if (prop.column.equals(aprop.columnName)) {
							prop.manyToOne = true;
							break;
						}
					}
					entity.propList.add(prop);
				}
			}
		}
		rs.close();
		if (entity.hasHibernateVersion == false) {
			System.out.println("Warning: table[" + entity.tableName + "] dosn't have a hibernate_version column");
		}
		return entity;
	}

	public static void buildManyToMany(SetProperty sprop, DatabaseMetaData dbmd) throws Exception {
		ResultSet rs = dbmd.getPrimaryKeys(null, null, sprop.foreignTableName);
		int primaryKeyNumber = 0;
		while (rs.next()) {
			primaryKeyNumber++;
		}
		if (primaryKeyNumber > 1) {
			sprop.manyToMany = true;
			rs.close();

			rs = dbmd.getImportedKeys(null, null, sprop.foreignTableName);
			while (rs.next()) {
				if (!sprop.foreignColumnName.equals(rs.getString("FKCOLUMN_NAME"))) {
					sprop.manyToManyTable = sprop.foreignTableName;
					sprop.foreignTableName = rs.getString("PKTABLE_NAME");
					sprop.foreignClassName = fillUpperName(removePrefix(sprop.foreignTableName, tablePrefix));
					sprop.propName = fillFirstLower(sprop.foreignClassName) + "Set";
					sprop.getMethod = buildGetMethod(sprop.propName);
					sprop.setMethod = buildSetMethod(sprop.propName);
					sprop.manyForeignColumnName = rs.getString("FKCOLUMN_NAME");
					break;
				}
			}
			rs.close();
		}
	}

	public static String fillUpperName(String underline) {
		String lower = underline2camel(underline);
		return fillFirstUpper(lower);
	}

	public static String fillLowerName(String underline) {
		return underline2camel(underline);
	}

	public static String fillFirstUpper(String name) {
		return name.substring(0, 1).toUpperCase() + name.substring(1);
	}

	public static String fillFirstLower(String name) {
		return name.substring(0, 1).toLowerCase() + name.substring(1);
	}

	public static String underline2camel(String underline) {
		StringBuilder sb = new StringBuilder();
		String temp = underline;
		while (true) {
			int index = temp.indexOf("_");
			if (index == -1) {
				sb.append(temp);
				break;
			} else {
				sb.append(temp.substring(0, index));
				sb.append(temp.substring(index + 1, index + 2).toUpperCase());
				temp = temp.substring(index + 2);
			}
		}
		return sb.toString();
	}

	public static String removePrefix(String tableName, String prefix) {
		if (tableName.startsWith(prefix)) {
			return tableName.substring(prefix.length());
		} else {
			return tableName;
		}
	}

	public static String buildGetMethod(String field, boolean isBoolean) {
		return (isBoolean ? "is" : "get") + field.substring(0, 1).toUpperCase() + field.substring(1);
	}

	public static String buildGetMethod(String field) {
		return buildGetMethod(field, false);
	}

	public static String buildSetMethod(String field) {
		return "set" + field.substring(0, 1).toUpperCase() + field.substring(1);
	}

	public static final Map<String, String> typeMap = new HashMap<String, String>();
	public static final Map<String, String> simpleTypeMap = new HashMap<String, String>();
	static {
		/*
		 * typeMap.put("INT", "java.lang.Integer"); typeMap.put("BIGINT",
		 * "java.lang.Long"); typeMap.put("TINYINT", "java.lang.Boolean");
		 * typeMap.put("DATE", "java.util.Date"); typeMap.put("DATETIME",
		 * "java.util.Date"); typeMap.put("TEXT", "java.lang.String");
		 * typeMap.put("VARCHAR", "java.lang.String"); typeMap.put("DECIMAL",
		 * "java.math.BigDecimal"); typeMap.put("BIT", "java.lang.Boolean");
		 * typeMap.put("TIMESTAMP", "java.util.Date"); typeMap.put("FLOAT",
		 * "java.lang.Float"); typeMap.put("MEDIUMTEXT", "java.lang.String");
		 * typeMap.put("CHAR", "java.lang.String"); typeMap.put("ENUM",
		 * "java.lang.String"); typeMap.put("DOUBLE", "java.lang.Double");
		 */

		typeMap.put("INT", "INTEGER");
		typeMap.put("BIGINT", "BIGINT");
		typeMap.put("TINYINT", "TINYINT");
		typeMap.put("DATE", "DATE");
		typeMap.put("TIMESTAMP", "TIMESTAMP");
		typeMap.put("TEXT", "VARCHAR");
		typeMap.put("VARCHAR", "VARCHAR");
		typeMap.put("DECIMAL", "DECIMAL");
		typeMap.put("BIT", "BIT");
		typeMap.put("TIMESTAMP", "TIMESTAMP");
		typeMap.put("FLOAT", "FLOAT");
		typeMap.put("MEDIUMTEXT", "MEDIUMTEXT");
		typeMap.put("CHAR", "CHAR");
		typeMap.put("ENUM", "ENUM");
		typeMap.put("DOUBLE", "DOUBLE");

		simpleTypeMap.put("INT", "Integer");
		simpleTypeMap.put("BIGINT", "Long");
		simpleTypeMap.put("TINYINT", "Boolean");
		simpleTypeMap.put("DATE", "Date");
		simpleTypeMap.put("DATETIME", "Date");
		simpleTypeMap.put("TEXT", "String");
		simpleTypeMap.put("VARCHAR", "String");
		simpleTypeMap.put("DECIMAL", "BigDecimal");
		simpleTypeMap.put("BIT", "Boolean");
		simpleTypeMap.put("TIMESTAMP", "Date");
		simpleTypeMap.put("FLOAT", "Float");
		simpleTypeMap.put("MEDIUMTEXT", "String");
		simpleTypeMap.put("CHAR", "String");
		simpleTypeMap.put("ENUM", "String");
		simpleTypeMap.put("DOUBLE", "Double");
	}
}
